#include "ProgramParser.h"

// properties

int ProgramParser::programLevel = 0;
std::stringstream ProgramParser::debugOutput;
const char* ProgramParser::keywords[N_KEYWORDS] = {
	KEYWORD_CALL,
	KEYWORD_IF,
	KEYWORD_PROCEDURE,
	KEYWORD_WHILE,
	KEYWORD_ELSE,
	KEYWORD_THEN
};

// constructors

ProgramParser::ProgramParser() {
	bool eof = false;
	std::string tempString;
	std::cout << "Enter your SIMPLE program below. Use 'EOF' to signifiy end: " << std::endl;
	while (!eof) {
		std::getline(std::cin, tempString);
		if (tempString.compare("EOF") == 0) {
			eof = true;
		} else {
			this->rawData.append(tempString + "\n");
		}
	}
}

ProgramParser::ProgramParser(const char* contents) {
	this->rawData = std::string(contents);
}

ProgramParser::ProgramParser(std::string fileName) {
	FileOperation file(fileName);
	try {
		file.loadContentsFromFile();
	} catch (ExceptionFileDoesNotExist ex) {
		std::cout << "ERROR: " << ex.what() << std::endl;
	}
	this->rawData = file.getContents();
}

// protected methods

unsigned int ProgramParser::whereCurrentLineEnds() {
	unsigned int endPosition = getMin(3,
		this->rawData.find('{', this->parsePosition),
		this->rawData.find('}', this->parsePosition),
		this->rawData.find(';', this->parsePosition)
		);
	while (isspace(this->rawData[endPosition])) {
		--endPosition;
	}
	return endPosition;
}

std::string ProgramParser::getNextLine() {
	unsigned int eol = whereCurrentLineEnds();
	std::string returnValue;
	if (eol != std::string::npos) {
		try {
			while (isspace(this->rawData[this->parsePosition])) {
				++this->parsePosition;
			}
			returnValue = this->rawData.substr(this->parsePosition, eol - this->parsePosition);
			this->parsePosition = eol + 1;
		} catch (std::exception ex) {
			std::cerr << ex.what() << std::endl;
		}
	} else {
		returnValue = "";
	}
	/*
	switch (this->rawData[eol]) {
	case '{':
	std::cout << "[[[ START OF A STMTLST ]]]" << std::endl;
	// to ast -> go to last created child stmtlst
	break;
	case '}':
	std::cout << "[[[ END OF CURRENT STMTLST ]]]" << std::endl;
	// to ast -> done with current stmtlst! go to parentNode
	break;
	case ';':
	std::cout << "[[[ END OF A LINE ]]]" << std::endl;
	break;
	}
	*/
	return returnValue;
}

bool ProgramParser::nextLineExists() {
	return ( // next line exists if
		(this->rawData.find('{', this->parsePosition) != std::string::npos) || // still got '{'
		(this->rawData.find('}', this->parsePosition) != std::string::npos) || // or still got '}'
		(this->rawData.find(';', this->parsePosition) != std::string::npos) // or still got ';'
		);
}

bool ProgramParser::isAssign(std::string line) {
	return false; // TODO
}
bool ProgramParser::isCall(std::string line) {
	return (line.find(KEYWORD_CALL, 0) == 0);
}
bool ProgramParser::isProcedure(std::string line) {
	return (line.find(KEYWORD_PROCEDURE, 0) == 0);
}
bool ProgramParser::isWhile(std::string line) {
	return (line.find(KEYWORD_WHILE, 0) == 0);
}
bool ProgramParser::isIfThen(std::string line) {
	return (line.find(KEYWORD_IF, 0) == 0);
}
bool ProgramParser::isElse(std::string line) {
	return (line.find(KEYWORD_ELSE, 0) == 0);
}
bool ProgramParser::isClosure(std::string line) {
	return (line.length() == 0);
}

ParserObject ProgramParser::parseAssign(std::string line) {
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_ASSIGN;
	return ParserObject(PARSER_OBJECT_TYPE_ASSIGN, pObjectValues);
}

ParserObject ProgramParser::parseCall(std::string line) {
	// handle call
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_CALL;
	unsigned int afterKeyword = strlen(KEYWORD_CALL);
	// get the name of the procedure being called
	std::string procName;
	while (isspace(line[afterKeyword])) {
		++afterKeyword;
	}
	while ((afterKeyword < line.size())) {
		if (isalnum(line[afterKeyword])) {
			procName += line[afterKeyword++];
		}
	}
	debugOutput << "[[[ " << KEYWORD_CALL << "ing procedure:" << procName << " ]]]" << std::endl;
	debugOutput <<
		"\t" << "AST> Create CALL node (newNode)" << std::endl <<
		"\t" << "AST> Link newNode to currentNode as child" << std::endl <<
		"\t" << "AST> Set parentNode to currentNode" << std::endl <<
		"\t" << "AST> Set currentNode to newNode" << std::endl <<
		"\t" << "AST> Create a VALUE node with value \"" << procName << "\" and link to currentNode" << std::endl <<
		"\t" << "AST> Set currentNode to parentNode" << std::endl
		<< std::endl;
	pObjectValues[PARSER_OBJECT_DATA_PROCEDURE_NAME] = procName;
	return ParserObject(PARSER_OBJECT_TYPE_CALL, pObjectValues);
}

ParserObject ProgramParser::parseProcedure(std::string line) {
	// handle procedures
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_PROCEDURE;
	++this->programLevel;
	unsigned int afterKeyword = strlen(KEYWORD_PROCEDURE);
	// get the name of the procedure being called
	std::string procName;
	while (isspace(line[afterKeyword])) {
		++afterKeyword;
	}
	while (afterKeyword < line.length()) {
		if (isalnum(line[afterKeyword])) {
			procName += line[afterKeyword];
		}
		++afterKeyword;
	}
	debugOutput << "[[[ start of procedure:" << procName << " (programLevel going to:" << this->programLevel << ") ]]]" << std::endl;
	debugOutput <<
		"\t" << "AST> Create PROCEDURE node (newNode)" << std::endl <<
		"\t" << "AST> Link newNode as child of currentNode" << std::endl <<
		"\t" << "AST> Set currentNode to newNode" << std::endl <<
		"\t" << "AST> Create STMTLST node (newNode)" << std::endl <<
		"\t" << "AST> Set currentNode to newNode" << std::endl
		<< std::endl;
	char *temp = (char*)malloc(10);
	pObjectValues[PARSER_OBJECT_DATA_PROGRAM_LEVEL] = itoa(this->programLevel, temp, 10);
	pObjectValues[PARSER_OBJECT_DATA_PROCEDURE_NAME] = procName;
	free(temp);
	return ParserObject(PARSER_OBJECT_TYPE_PROCEDURE, pObjectValues);
}

ParserObject ProgramParser::parseWhile(std::string line) {
	// handle while
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_WHILE;
	++this->programLevel;
	unsigned int afterKeyword = strlen(KEYWORD_WHILE);
	std::string conditionExpression;
	while (isspace(line[afterKeyword])) {
		++afterKeyword;
	}
	unsigned int endOfLine = line.length() - 1;
	while (!isalnum(line[endOfLine])) {
		--endOfLine;
	}
	conditionExpression = line.substr(afterKeyword, (endOfLine - afterKeyword + 1));
	debugOutput << "[[[ while loop with condition:(" << conditionExpression << ") (programLevel going to:" << this->programLevel << ")  ]]]" << std::endl;
	debugOutput <<
		"\t" << "AST> Create WHILE node (newNode)" << std::endl <<
		"\t" << "AST> Link newNode to currentNode as child" << std::endl <<
		"\t" << "AST> Create STMTLST node (newNode)" << std::endl <<
		"\t" << "AST> Set currentNode to newNode" << std::endl
		<< std::endl;
	// to ast -> create newNode & link to currentNode
	// to ast -> set currentNode as newNode
	// to ast -> create a new stmtlst node & link to currentNode
	char *temp = (char*)malloc(10);
	pObjectValues[PARSER_OBJECT_DATA_PROGRAM_LEVEL] = itoa(this->programLevel, temp, 10);
	free(temp);
	pObjectValues[PARSER_OBJECT_DATA_CONDITION] = conditionExpression;
	return ParserObject(PARSER_OBJECT_TYPE_WHILE, pObjectValues);
}

ParserObject ProgramParser::parseElse(std::string line) {
	// handle elses
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_ELSE;
	++this->programLevel;
	debugOutput << "[[[ or else... (programLevel going to:" << this->programLevel << ") ]]]" << std::endl;
	if (line[strlen(KEYWORD_ELSE) - 1] < line.length()) {
		if (isspace(line[strlen(KEYWORD_ELSE)])) {
			unsigned int iteratorPosition = strlen(KEYWORD_ELSE);
			while (iteratorPosition != line.length() - 1) {
				if (!isspace(line[iteratorPosition])) {
					// throw unknown exception (else should be a line on it's own)
				} else {
					++iteratorPosition;
				}
			}
		}
	}
	debugOutput <<
		"\t" << "AST> Create STMTLST node (elseNode)" << std::endl <<
		"\t" << "AST> Link elseNode as child of currentNode (should be an IF node)" << std::endl <<
		"\t" << "AST> Set currentNode to elseNode" << std::endl
		<< std::endl;
	char *temp = (char*)malloc(10);
	pObjectValues[PARSER_OBJECT_DATA_PROGRAM_LEVEL] = itoa(this->programLevel, temp, 10);
	free(temp);
	return ParserObject(PARSER_OBJECT_TYPE_ELSE, pObjectValues);
}

ParserObject ProgramParser::parseIfThen(std::string line) {
	// handle if
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_IF_THEN;
	++this->programLevel;
	unsigned int afterKeyword = strlen(KEYWORD_IF);
	std::string conditionExpression;
	// trim spaces between 'if' keyword and start of expression
	while (isspace(line[afterKeyword])) {
		++afterKeyword;
	}
	// at this point afterKeyword contains the start of the CONDITION
	unsigned int endOfConditionExpression = line.find(KEYWORD_THEN, afterKeyword) - 1;
	// the end of the condition is marked by the 'then' keyword
	if (endOfConditionExpression == std::string::npos) {
		// throw exception
		debugOutput << "lol?" << std::endl;
	} else {
		// trim spaces between end of CONDITION and 'then' keyword
		while (isspace(line[endOfConditionExpression])) {
			--endOfConditionExpression;
		}
	}
	conditionExpression = line.substr(afterKeyword, (endOfConditionExpression - afterKeyword + 1));
	debugOutput << "[[[ if statement with condition:(" << conditionExpression <<
		") (programLevel going to:" << this->programLevel <<
		")  ]]]" << std::endl;
	debugOutput <<
		"\t" << "AST> Create IF node (newNode)" << std::endl <<
		"\t" << "AST> Link newNode to currentNode as child" << std::endl <<
		"\t" << "AST> Set currentNode to newNode" << std::endl <<
		"\t" << "AST> Create CONDITION node (newNode)" << std::endl <<
		"\t" << "AST> Link newNode to currentNode as child" << std::endl <<
		"\t" << "AST> Create STMTLST node (thenNode)" << std::endl <<
		"\t" << "AST> Link thenNode to currentNode as child" << std::endl <<
		"\t" << "AST> Set currentNode to thenNode" << std::endl
		<< std::endl;
	char *temp = (char*)malloc(10);
	pObjectValues[PARSER_OBJECT_DATA_PROGRAM_LEVEL] = itoa(this->programLevel, temp, 10);
	pObjectValues[PARSER_OBJECT_DATA_CONDITION] = conditionExpression;
	free(temp);
	return ParserObject(PARSER_OBJECT_TYPE_IF_THEN, pObjectValues);
}

ParserObject ProgramParser::parseClosure(std::string line) {
	// handle a closure of stmtlst,
	std::vector<std::string> pObjectValues(10, "");
	pObjectValues[PARSER_OBJECT_DATA_INDEX_TYPE] = PARSER_OBJECT_TYPE_CLOSURE;
	--this->programLevel;
	debugOutput << "[[[ STMTLST END (programLevel now at:" << this->programLevel << ") ]]]" << std::endl;
	debugOutput <<
		"\t" << "AST> We're done! Go to whoever your parentNode is" << std::endl
		<< std::endl;
	char *temp = (char*)malloc(10);
	pObjectValues[PARSER_OBJECT_DATA_PROGRAM_LEVEL] = itoa(this->programLevel, temp, 10);
	return ParserObject(PARSER_OBJECT_TYPE_CLOSURE);
}

ParserObject ProgramParser::parseError(std::string line) {
	debugOutput << "[[[ OTHER UNDEFINED ACTIVITY ]]]" << std::endl;
	return ParserObject();
}

ParserObject ProgramParser::processLine(std::string line) {
	try {
		//debugOutput << "[[[ START LINE ]]]_" << line << std::endl;
		if (isIfThen(line)) {
			return parseIfThen(line);
		} else if (isWhile(line)) {
			return parseWhile(line);
		} else if (isCall(line)) {
			return parseCall(line);
		} else if (isProcedure(line)) {
			return parseProcedure(line);
		} else if (isElse(line)) {
			return parseElse(line);
		} else if (isClosure(line)) {
			return parseClosure(line);
		} else if (isAssign(line)) {
			return parseAssign(line);
		} else {
			return parseError(line);
		}
		//debugOutput << line << "_[[[ END LINE ]]]" << std::endl;
	} catch (std::exception ex) {
		std::cout << ex.what() << std::endl;
		std::vector<std::string> exceptionMessage(1, ex.what());
		return ParserObject(PARSER_OBJECT_TYPE_GENERIC_EXCEPTION, exceptionMessage);
	}
	return ParserObject();
}

// public methods

void ProgramParser::parse() {
	debugOutput.str("");
	this->parsePosition = 0;
	this->programLevel = 0;
	//const char* currentLine;
	// take in new line (each line delimited by any of the following: ; , { , }
	int counter = 0;
	while (nextLineExists()) {
		processedData.push_back(processLine(getNextLine()));
		++counter;
	}
	if (this->programLevel != 0) {
		// throw exception
		//throw ExceptionUnexpectedEndOfFile();
		debugOutput << "[EXCEPTION] Unexpected end of file" << std::endl;
	}
}

std::string ProgramParser::getDebugOutput() {
	return debugOutput.str();
}

std::string ProgramParser::getRawData() {
	return this->rawData;
}

ParserObject &ProgramParser::operator[](unsigned int index) {
	return processedData[index];
}

unsigned int ProgramParser::size() {
	return processedData.size();
}